package nayasis.common.util;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import nayasis.common.exception.unchecked.ReflectiveException;

/**
 * Reflection을 처리하는 유틸 클래스
 *
 * @author 정화수
 *
 */
public abstract class ReflectionUtil {

    private ReflectionUtil() {}

    /**
     * 객체에 선언된 field 목록을 가져온다.
     *
     * @param object 조사할 객체
     * @return 객체에 선언된 field 목록
     */
    public static Field[] getFields( Object object ) {

        return object.getClass().getDeclaredFields();

    }

    /**
     * 객체에 선언된 field 이름 목록을 가져온다.
     *
     * @param object 조사할 객체
     * @return 객체에 선언된 field 목록
     */
    public static List<String> getFieldNames( Object object ) {

        Field[] declaredFields = object.getClass().getDeclaredFields();

        List<String> result = new ArrayList<String>();

        for( Field field : declaredFields ) {

            result.add( field.getName() );

        }

        return result;

    }

    public static List<String> getFieldNames( Map<?,?> object ) {

        List<String> result = new ArrayList<String>();

        Set<?> keySet = object.keySet();

        for( Object key : keySet ) {

            result.add( key.toString() );

        }

        return result;

    }

    /**
     * 객체에 선언된 field에 담긴 값을 가져온다.
     *
     * @param object 조사할 객체
     * @param field 객체에 선언된 field
     * @return field에 담겨있는 값
     */
    public static Object getFieldValue( Object object, Field field ) {

        field.setAccessible( true );

        try {
            return field.get( object );

        } catch ( ReflectiveOperationException e ) {
            throw new ReflectiveException( e.getMessage(), e );
        }

    }

    /**
     * 객체에 선언된 field에 담긴 값을 가져온다.
     *
     * @param object 조사할 객체
     * @param fieldName 객체에 선언된 field 명
     * @return field에 담겨있는 값
     * @throws ReflectiveOperationException field 접근 실패시
     */
    public static Object getFieldValue( Object object, String fieldName ) {

        try {

            Field field = object.getClass().getField( fieldName );
            field.setAccessible( true );

            return field.get( object );

        } catch ( ReflectiveOperationException e ) {
            throw new ReflectiveException( e.getMessage(), e );
        }


    }

    /**
     * 객체에 선언된 field에 값을 담는다.
     *
     * @param object 조사할 객체
     * @param field 객체에 선언된 field
     * @throws ReflectiveOperationException field 접근 실패시
     */
    public static void setFieldValue( Object object, Field field, Object value ) {

        field.setAccessible( true );

        try {
            field.set( object, value );

        } catch ( ReflectiveOperationException e ) {
            throw new ReflectiveException( e.getMessage(), e );
        }

    }

    /**
     * 객체에 선언된 field에 값을 담는다.
     *
     * @param object 조사할 객체
     * @param fieldName 객체에 선언된 field 명
     * @param value field에 담을 값
     */
    public static void setFieldValue( Object object, String fieldName, Object value ) {

        Field field;

        try {

            field = object.getClass().getField( fieldName );

            field.setAccessible( true );

            field.set( object, value );

        } catch ( ReflectiveOperationException e ) {
            throw new ReflectiveException( e.getMessage(), e );
        }

    }

    /**
     * 객체의 field 이름과 값을 쌍으로 갖는 데이터를 구한다.
     *
     * @param object 조사할 객체
     * @return field 이름과 값을 쌍으로 갖는 데이터
     */
    public static Map<String, Object> getFieldMap( Object object ) {

        Field[] declaredFields = object.getClass().getDeclaredFields();

        Map<String, Object> result = new HashMap<String, Object>();

        for( Field field : declaredFields ) {

            field.setAccessible( true );

            try {
                result.put( field.getName(), field.get(object) );
            } catch ( ReflectiveOperationException e ) {
                throw new ReflectiveException( e.getMessage(), e );
            }

        }

        return result;

    }

    /**
     * 객체를 복사한다. (객체에 담긴 데이터까지 모두 포함)
     * 
     * @param objectToClone 복사할 객체
     * @return 복사한 객체
     * @throws IOException, ClassNotFoundException 
     */
    @SuppressWarnings( "unchecked" )
    public static <T> T deepClone( T objectToClone ) throws IOException, ClassNotFoundException {

        if( objectToClone == null ) return null;
        
        ObjectOutputStream oos = null;
        ObjectInputStream  ois = null;

        try {
            
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream( bos );

            oos.writeObject( objectToClone );
            oos.flush();

            ByteArrayInputStream bis = new ByteArrayInputStream( bos.toByteArray() );
            ois = new ObjectInputStream( bis );

            return (T) ois.readObject();
            
        } catch( IOException e ) {
            throw e;

        } finally {

            if( oos != null ) {
                try { oos.close(); } catch ( Exception e ) {}
            }
            if( ois != null ) {
                try { ois.close(); } catch ( Exception e ) {}
            }

        }
        
    }
    
}
